今天這個章節主要是利用電腦的視訊鏡頭,以canvas為基底,去改變我們的畫面色彩、透明度等等。
而我就依照比較重要的重點來分享。
// 監聽是否video正在播放('canplay')
video.addEventListener('canplay', paintToCanvas);
navigator.mediaDevices
可用來串聯與麥克風、攝影機或共享螢幕的連結。getUserMedia()
可以用來顯示是否允許開啟連結至其設備。getUserMedia(constraints)
的constraints參數有2個,分別為video和audio,此處我們只需要影片不需要音檔
且getUserMedia()會返回一個Promise物件
,我們需要利用.then來取得其返回的MediaStream物件
取得MediaStream物件後,我們去處理video的來源,由於作者提示說Chorme、firefox已經不支援video.src = window.URL.createObjectURL(localMediaStream)這個方法,而是改採video.srcObject = localMediaStream;
,但由於還要顧及其他瀏覽器,如IE因此我們利用try、catch來寫兼容寫法。
// 獲取video並開始播放
function getVideo() {
// 要video但不要audio
// MediaDevices.getUserMedia()
// 並且會提示說是否要開啟視訊鏡頭
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
})
.then(localMediaStream => {
console.log(localMediaStream);
// MediaStream
// active: true
// id: "sbREnyqNcRJEaadztxfa2cbVz38AnbBmIyiJ"
// onactive: null
// onaddtrack: null
// oninactive: null
// onremovetrack: null
try {
video.srcObject = localMediaStream;
} catch (err) {
console.error(`OH NO!!!`, err);
video.src = window.URL.createObjectURL(localMediaStream);
}
// 原始畫面
video.play();
})
}
利用上下文創建一個二維的畫布。
const canvas = document.querySelector('.photo');
// ctx為我們所要畫的對象
const ctx = canvas.getContext('2d');
image:放canvas的圖像來源。
dx, dy:起始位置(x,y)。
dWidth, dHeight:設定寬高。
sx:複製左上角的x座標。
sy:複製左上角的x座標。
sw:複製的寬度。
sh:複製的高度。
getImageData 跟putImageData是一組的,它可以讓你取得每個像素的rgba值然後作變化放回去
。
此處我們設置一個名為pixels的變數,來觀察普通面積與pixels的差別,一個點會被拆分成rgba,也就是pixels為area的四倍,可觀察出面積與點的關係會差四倍
,會看到我們儲存像素點的地方是一個叫做Uint8ClampedArray的array,其內共有1228800個數值所組成,4個為一組,表示一個rgba的像素點要呈現的顏色
。
let pixels = ctx.getImageData(0, 0, width, height);
console.log(pixels); ImageData {data: Uint8ClampedArray(1228800), width: 640, height: 480}
console.log(`Area: ${width * height},Pixels ${pixels.data.length}`); Area: 307200,Pixels 1228800
console.log(pixels.data[0], pixels.data[1], pixels.data[2], pixels.data[3]);
// 106 114 55 255
console.log(pixels.data[0 + 4], pixels.data[1 + 4], pixels.data[2 + 4], pixels.data[3 + 4]);
// 109 116 57 255
因為影片會一直更新,所以設置計時器讓他一直跑,我們所做的視訊效果也是在這邊呼叫。
return setInterval(() => {
// 畫一個video,從起始位置(0,0),畫width,height的寬高
ctx.drawImage(video, 0, 0, width, height);
// take the pixels out
let pixels = ctx.getImageData(0, 0, width, height);
// mess with them
// pixels = redEffect(pixels);
// console.log(pixels.data[0], pixels.data[1], pixels.data[2], pixels.data[3]);
// pixels = rgbSplit(pixels);
// ctx.globalAlpha = 0.8;
pixels = greenScreen(pixels);
// put them back
ctx.putImageData(pixels, 0, 0);
}, 16);
strip.insertBefore(link, strip.firstChild)
,在父元素下,將每次新產的link插入至第一個子元素的最前面
。
type:圖像格式,預設為 image/png.
,表示 image/jpeg或是image/webp的圖像品質,不加就為預設。
此處為拍照功能的部分。
function takePhoto() {
// played the sound
snap.currentTime = 0;
snap.play();
// 做截圖的功能
// canvas.toDataURL(type, encoderOptions);
// type:圖像格式 預設為 image/png. 表示 image/jpeg 或是 image/webp 的圖像品質 不加就為預設。
// 回傳含有圖像和參數設置特定格式的 data URIs (預設 PNG). 回傳的圖像解析度為 96 dpi
const data = canvas.toDataURL('image/jpeg');
const link = document.createElement('a');
link.href = data;
// 創建一個download屬性,其預設值為handsome,當點擊其元素就會跳出另存新檔
link.setAttribute('download', 'handsome');
// 將圖加至link中
link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;
// Node.insertBefore() 方法將一個節點插入至參考節點之前
// parentNode.insertBefore(newNode, referenceNode);
// 將每次新產的link插入至第一個子元素的最前面
strip.insertBefore(link, strip.firstChild);
}
以下幾種功能,主要都是以四個色板rgba對應0,1,2,3下去做調整,i+=4的原因是因為rgba四個為一組,所以每次跳四個才會到下一個rgba。
將r部分的顏色提高,其餘減少。
function redEffect(pixels) {
// console.log(pixels.data.length); // 1228800
for (let i = 0; i < pixels.data.length; i += 4) {
// 在什麼位置對其做調色
pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED
pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN
pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue
}
return pixels;
}
將rgb不同的顏色移動到不同的位置即可。
function rgbSplit(pixels) {
for (let i = 0; i < pixels.data.length; i += 4) {
// 固定的顏色對不同位置去做變化(漸層)
pixels.data[i - canvas.width * 4 * 50] = pixels.data[i + 0]; // RED
pixels.data[i - canvas.width * 4 * 30] = pixels.data[i + 1]; // GREEN
pixels.data[i - canvas.width * 4 * 10] = pixels.data[i + 2]; // Blue
}
return pixels;
}
只要顏色落在指定區間,就讓他變透明(a變為0)。
function greenScreen(pixels) {
const levels = {};
document.querySelectorAll('.rgb input').forEach((input) => {
levels[input.name] = input.value;
});
// 獲取對應色板
for (i = 0; i < pixels.data.length; i = i + 4) {
red = pixels.data[i + 0];
green = pixels.data[i + 1];
blue = pixels.data[i + 2];
alpha = pixels.data[i + 3];
// 如果紅綠藍介於最大與最小之間,
if (red >= levels.rmin &&
green >= levels.gmin &&
blue >= levels.bmin &&
red <= levels.rmax &&
green <= levels.gmax &&
blue <= levels.bmax) {
// 將alpha設為0
pixels.data[i + 3] = 0;
}
}
return pixels;
}
加重黃紅比例即可。
function oldStyle(pixels) {
for (let i = 0; i < pixels.data.length; i += 4) {
pixels.data[i + 0] += 150; //red
pixels.data[i + 1] += 60; //green
pixels.data[i + 2] += 60; //blue
pixels.data[i + 3] *= 0.8; //alpha
}
return pixels;
}
將顏色變成互補色,利用255去減掉原本取得的顏色即可。
function negativeEffect(pixels) {
for (let i = 0; i < pixels.data.length; i += 4) {
pixels.data[i + 0] = 255 - pixels.data[i + 0]; //red
pixels.data[i + 1] = 255 - pixels.data[i + 1]; //green
pixels.data[i + 2] = 255 - pixels.data[i + 2]; //blue
}
return pixels;
}